
#include <pam_appl.h>
#include <stdio.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>


#define get_int16(s) ((((unsigned char*)  (s))[0] << 8) | \
                      (((unsigned char*)  (s))[1]))


#define put_int16(i, s) {((unsigned char*)(s))[0] = ((i) >> 8) & 0xff; \
                        ((unsigned char*)(s))[1] = (i)         & 0xff;}

#ifndef D
/*#define D(str) fprintf(stderr, (str)) */
#define D(str)
#endif

static int read_fill(int fd, unsigned char *buf, int len)
{
    int i, got = 0;

    do {
        if ((i = read(fd, buf+got, len-got)) <= 0) {
            if (i == 0) return got;
            if (errno != EINTR)
                return got;
            i = 0;
        }
        got += i;
    } while (got < len);
    return (len);
}


    
static int write_fill(int fd, char *buf, int len)
{
    int i, done = 0; 
    
    do {
        if ((i = write(fd, buf+done, len-done)) < 0) {
            if (errno != EINTR)
                return (i);
            i = 0;
        }
        done += i;
    } while (done < len);
    return (len);
}


#if 0
/*
 * These functions are for binary prompt manipulation.
 * The manner in which a binary prompt is processed is application
 * specific, so these function pointers are provided and can be
 * initialized by the application prior to the conversation function
 * being used.
 */

static void pam_misc_conv_delete_binary(void *appdata,
					pamc_bp_t *delete_me)
{
    PAM_BP_RENEW(delete_me, 0, 0);
}

int (*pam_binary_handler_fn)(void *appdata, pamc_bp_t *prompt_p) = NULL;
void (*pam_binary_handler_free)(void *appdata, pamc_bp_t *prompt_p)
      = pam_misc_conv_delete_binary;
#endif

/*
 * This conversation function is supposed to be a generic PAM one.
 * Unfortunately, it is _not_ completely compatible with the Solaris PAM
 * codebase.
 *
 * Namely, for msgm's that contain multiple prompts, this function
 * interprets "const struct pam_message **msgm" as equivalent to
 * "const struct pam_message *msgm[]". The Solaris module
 * implementation interprets the **msgm object as a pointer to a
 * pointer to an array of "struct pam_message" objects (that is, a
 * confusing amount of pointer indirection).
 */

int misc_conv(int num_msg, const struct pam_message **msgm,
	      struct pam_response **response, void *appdata_ptr)
{
    int count=0;
    struct pam_response *reply;

    if (num_msg <= 0)
	return PAM_CONV_ERR;

    D(("allocating empty response structure array."));

    reply = (struct pam_response *) calloc(num_msg,
					   sizeof(struct pam_response));
    if (reply == NULL) {
	D(("no memory for responses"));
	return PAM_CONV_ERR;
    }

    D(("entering conversation function."));

    for (count=0; count < num_msg; ++count) {
	char *string=NULL;

	switch (msgm[count]->msg_style) {
	case PAM_PROMPT_ECHO_OFF:
	    string = (char*)appdata_ptr;
	    break;
	case PAM_PROMPT_ECHO_ON:
	    string = (char*)appdata_ptr;
	    break;
	case PAM_ERROR_MSG:
	    if (fprintf(stderr,"%s\n",msgm[count]->msg) < 0) {
		goto failed_conversation;
	    }
	    break;
	case PAM_TEXT_INFO:
	    if (fprintf(stdout,"%s\n",msgm[count]->msg) < 0) {
		goto failed_conversation;
	    }
	    break;
#if 0
	case PAM_BINARY_PROMPT:
	{
	    pamc_bp_t binary_prompt = NULL;

	    if (!msgm[count]->msg || !pam_binary_handler_fn) {
		goto failed_conversation;
	    }

	    PAM_BP_RENEW(&binary_prompt,
			 PAM_BP_RCONTROL(msgm[count]->msg),
			 PAM_BP_LENGTH(msgm[count]->msg));
	    PAM_BP_FILL(binary_prompt, 0, PAM_BP_LENGTH(msgm[count]->msg),
			PAM_BP_RDATA(msgm[count]->msg));

	    if (pam_binary_handler_fn(appdata_ptr,
				      &binary_prompt) != PAM_SUCCESS
		|| (binary_prompt == NULL)) {
		goto failed_conversation;
	    }
	    string = (char *) binary_prompt;
	    binary_prompt = NULL;

	    break;
	}
#endif
	default:
	    fprintf(stderr, "erroneous conversation (%d)\n"
		    ,msgm[count]->msg_style);
	    goto failed_conversation;
	}

	if (string) {                         /* must add to reply array */
	    /* add string to list of responses */

	    reply[count].resp_retcode = 0;
	    reply[count].resp = string;
	    string = NULL;
	}
    }

    *response = reply;
    reply = NULL;

    return PAM_SUCCESS;

failed_conversation:

    D(("the conversation failed"));

    if (reply) {
	for (count=0; count<num_msg; ++count) {
	    if (reply[count].resp == NULL) {
		continue;
	    }
	    switch (msgm[count]->msg_style) {
	    case PAM_PROMPT_ECHO_ON:
	    case PAM_PROMPT_ECHO_OFF:
#if 0
		_pam_overwrite(reply[count].resp);
#endif
		free(reply[count].resp);
		break;
#if 0
	    case PAM_BINARY_PROMPT:
		pam_binary_handler_free(appdata_ptr,
					(pamc_bp_t *) &reply[count].resp);
		break;
#endif
	    case PAM_ERROR_MSG:
	    case PAM_TEXT_INFO:
		/* should not actually be able to get here... */
		free(reply[count].resp);
	    }                                            
	    reply[count].resp = NULL;
	}
	/* forget reply too */
	free(reply);
	reply = NULL;
    }

    return PAM_CONV_ERR;
}






static struct pam_conv conv = {
    misc_conv,
    NULL
};

static void werr(pam_handle_t *pamh, int sid, int ecode, char *phase)
{
    char buf[BUFSIZ];
    int len;

    sprintf(&buf[2], "pam %d no %s %s",
	    sid, phase, pam_strerror(pamh, ecode));
    len = strlen(&buf[2]);
    put_int16(len, &buf[0]);
    if (write_fill(1, buf, len+2) != len+2)
	exit(1);
}


static void wok(int sid)
{
    char buf[BUFSIZ];
    int len;

    sprintf(&buf[2], "pam %d yes", sid);
    len = strlen(&buf[2]);
    put_int16(len, &buf[0]);
    if (write_fill(1, buf, len+2) != len+2)
	exit(1);
}

static void wstart()
{
    char buf[5];


    sprintf(&buf[2], "ok");
    put_int16(2, &buf[0]);
    if (write_fill(1, buf, 4) != 4) {
	exit(1);
    }
}


struct session {
    pam_handle_t *pamh;
    int sid;
    int session_mode;
    struct session *next;
};

static struct session *sessions = NULL;

static struct session *del_session(struct session **sp, int sid)
{

    struct session *tmp;

    if (*sp == NULL) return NULL;
    if ((*sp)->sid == sid) {
	tmp = *sp;
	*sp = tmp->next;
	return tmp;
    }
    tmp = (*sp)->next;
    while (tmp != NULL) {
	if (tmp->sid == sid) {
	    (*sp)->next = tmp->next;
	    return tmp;
	}
	sp = &((*sp)->next);
	tmp = tmp->next;
    }
    return NULL;
}

	
static void do_auth(char *service, char*user, char*pwd, char* mode, int sid)
{
    pam_handle_t *pamh=NULL;
    int retval;
    struct session *sessp;

    conv.appdata_ptr = (void*)strdup(pwd);
    retval = pam_start(service, user, &conv, &pamh);
    
    if (retval != PAM_SUCCESS) {
	werr(pamh, sid, retval, "start");
	return;
    }
    pam_set_item(pamh, PAM_RUSER, user);

    retval = pam_authenticate(pamh, 0); 
    if (retval != PAM_SUCCESS) {
	werr(pamh, sid, retval, "auth");
	return;
    }
    if (mode[0] == 'A') {
	retval = pam_acct_mgmt(pamh, 0); 
	if (retval != PAM_SUCCESS) {
	    werr(pamh, sid, retval, "accounting");
	    return;
	}
	/*fprintf(stderr, "did ok acct \n\r");*/
    }
    if (mode[1] == 'S') {
	retval = pam_open_session(pamh, 0);
	if (retval != PAM_SUCCESS) {
	    werr(pamh, sid, retval, "session");
	    return;
	}
	/*fprintf(stderr, "did ok open sess \n\r"); */
    }
    if ((sessp = malloc(sizeof(struct session))) == NULL) {
	werr(pamh, sid, -1, "malloc");
	return;
    }
    if (mode[1] == 'S') 
	sessp->session_mode = 1;
    else
	sessp->session_mode = 0;
    sessp->sid = sid;
    sessp->pamh = pamh;
    sessp->next = sessions;
    sessions = sessp;
    
    wok(sid);
}


int main(int argc, char *argv[])
{
    pam_handle_t *pamh=NULL;
    unsigned char lb[2];
    unsigned char buf[BUFSIZ];
    char *user;
    char *pwd;
    char *mode;
    int sid;
    int rval;
    struct session *sessp;

    // test clause
    if (argc == 4 ) {
	/* ./epam authmodule user passwd */
	printf("testing service=%s u=%s pwd=%s\n", argv[1],argv[2], argv[3]);
	do_auth(argv[1], argv[2], argv[3], "AS", 33);
	exit(0);
    }
    wstart();
    while (1) {
        if (read_fill(0, lb, 2) != 2)
            exit(1);
        rval = get_int16(lb);
        if (read_fill(0, buf, rval) != rval)
            exit(1);
	switch (buf[0]) {
	case 'a': 
	    // auth a user
	    pamh = NULL;
	    user = (char *)&buf[1];
	    pwd = user + strlen(user) + 1;
	    mode= pwd + strlen(pwd) + 1;
	    sid = atoi(mode + strlen(mode) + 1);
	    
	    do_auth(argv[1], user, pwd, mode, sid);
	    break;
	case 'c': 
	    // close session
	    sid = atoi((char *)&buf[1]);
	    if ((sessp = del_session(&sessions, sid)) == NULL) {
		fprintf(stderr, "Couldn't find session %d\r\n", sid); 
		break;
	    }
	    if (sessp->session_mode == 1) {
		pam_close_session(sessp->pamh, 0);
		/*fprintf(stderr, "did ok close sess \n\r");*/
	    }
	    pam_end(sessp->pamh, PAM_SUCCESS); 
	    free(sessp);
	    break;
	default:
	    fprintf(stderr, "Bad op \n\r");
	}
    }
}


